iT邦幫忙

2023 iThome 鐵人賽

DAY 26
0

根據 Web Almanac 2022 年的統計,網頁的圖片大小佔整體網頁檔案大小,中位數超過 6 成。而 JavaScipt 則佔近 3 成。
median page weight
(圖片來源:https://almanac.httparchive.org/en/2022/page-weight#content-type-and-file-formats)

所以在 Next.js 13 效能優化的第一章,我們來關注兩點:

  1. 針對圖片載入的優化
  2. 針對瀏覽器載入 JavaScript 的優化

今天先來討論第一點,Next 針對圖片載入優化提供的 sloution:


常見圖片載入衍生問題

當頁面載入圖片時,常會碰到幾個問題:

  1. 圖片數量多,載入速度慢:
    假如沒特別處理,瀏覽器預設會等所有圖片都載入完成後才顯示頁面。因此當頁面圖片很多可能會影響到網頁載入速度。

    於是很多開發者會採用 lazy loading 的方式,讓瀏覽器只載入目前 viewport 用到的圖片,而不用等到所有圖片都下載完後才能顯示畫面。

    假如要做圖片的 lazy loading,可以使用 Web API 中的 Intersection Observer API。網路上已經很多相關教學,這邊就不多做介紹,有興趣了解的讀者可以直接搜尋「圖片 lazy loading」,或「Intersection Observer API」。

  2. Layout Shift:
    圖片載入完成後,推擠頁面其他元素,造成頁面 layout 改變。
    https://ithelp.ithome.com.tw/upload/images/20230925/20161853JCL6fSV5cR.png
    ( 圖片來源:https://productiveshop.com/cumulative-layout-shift/ )

    layout shift 也是 Google 搜尋排名的其中一項標準 - Cumulative Layout Shift (CLS),所以有些網頁會預留圖片的空間,或盡量使用 transform 等不會影響 layout 的方式顯示圖片。

  3. 圖片檔案大,載入時間長:
    有些網頁會特別將 PNG、JPEG 等圖片轉成 WebP、AVIF 等新型態的圖片格式,降低圖片檔案大小,且不影響圖片品質,以優化 UX 和 SEO。

但如同前面提到的諸多案例,每項優化都需要開發者額外花時間精力去處理,因此貼心的 Vercel 提供了一個 component next/image,在底層幫你做好以上優化。

next/image - <Image>

Next 延伸了 HTML <image> 元素,提供一個效能更優的 component - next/image

優化內容有幾項:

  1. 自動將圖片轉成 WebP/AVIF 格式
  2. 自動預防 layout shift
  3. 預設 lazy loading

要如何使用呢?

使用本機圖檔

類似 <img> 的使用方式,我們可以從 next/image import <Image> 後,在 src 指定圖片檔案路徑:

import Image from 'next/image';
import heroImage from './heroImage.png';

export default function Page() {
  return (
      <Image src={heroImage} alt='hero image />
  );
}

自動轉檔
可以從上面 sample code 看到,heroImage 的格式原本是 png,但假設進到頁面另存這張圖檔,會發現下載的圖檔格式為 WebP。因為使用 <Image>,Next 會自動幫你轉圖片格式。

Next 會依據 request header 中,Accept array 的順序來決定轉檔的格式。假如想自訂格式順位,可以在 next.config.js 中設定:

/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    formats: ['image/avif', 'image/webp'],
  },
};

module.exports = nextConfig;

這樣假如瀏覽器支援 AVIF,<Image> 的圖片就會優先轉為 AVIF。

自動設定圖片尺寸
假如使用本機圖檔,Next 會自動根據圖檔尺寸設定 <img> 的 width 和 height。為什麼要特別設定 width 跟 height 呢?為了預留位置給圖片,防止 layout shift。

假設我們在圖片下加一個 element:

import Image from 'next/image';
import heroImage from './heroImage.png';

export default function Page() {
  return (
    <>
      <Image src={heroImage} alt='hero image' />
      <h1>一起來欣賞美麗的風景</h1>
    </>
  );
}

可以發現圖片還載入完成前,網頁預留了圖片的區塊:
image with size

打開網頁原始碼,也可以發現 <img> 帶有 width 和 height 兩個 attributes。

但這時你可能會想:圖片載入成功前,畫面一塊白白的很奇怪,有辦法先顯示一個模糊的版本嗎?

blur-up placeholder
<Image> 可以帶一個 placehoder prop。加入 placeholder='blur' 就可以在圖片載完前先顯示模糊的版本:

<Image src={heroImage} alt='hero image' placeholder='blur' />

image with blur placeholder

next/image 不支援動態圖片匯入,例如await import()require()

預設 lazy loading
檢視網頁原始碼可發現,Next 幫我們在 <img> 加上loading="lazy" 的 attribute。

所以當圖片接近 viewport 時,瀏覽器才會載入圖片:
lazy loading demo

假如想捨棄 lazy loading,可以將 loading prop 改為 eager

/* 不會 lazy loading */
<Image src={heroImage} alt='hero image' loading='eager'/>

優先載入
假如頁面 LCP element 是一張圖片,我們可以在它的 <Image> 加上 priority prop,讓這張圖片優先載入。

假如 <Image> 的 priority 為 true,則會取消 lazy loading,優先載入:

export default function Page() {
  return (
    <>
      <Image src={image1} alt='image 1' />
      {/* 讓 image2 優先載入 */}
      <Image src={image2} alt='image2' priority />
    <>
  );
}

使用 Remote Images

假如圖片不是從本機匯入 (ex: 雲端),要做幾項設定:

  1. 須在 next.config.js 中設定支援的圖片來源資訊:

         /** @type {import('next').NextConfig} */
         const nextConfig = {
           images: {
             remotePatterns: [{
               protocol: 'https',
               hostname: 's3.amazonaws.com',
               port: '',
               pathname: '/my-bucket/**',
             }],
           },
         };
    
         module.exports = nextConfig;
    
  2. 須手動設定 width 跟 height:
    width 與 height 主要目的是預留位置給圖片,避免 layout shift,不會影到圖片顯示比例。

    假設 width 小於原圖的寬,則圖片會依照設定的 width 搭配對應的高顯示。比方說我的圖片比例為 4:3,尺寸為 800 * 600,假如我設定 <Image> 的 width 與 height 都為 400,那 loading 時預留的空間會是 400x400,最終圖片使寸會是 400x300。
    https://ithelp.ithome.com.tw/upload/images/20230926/20161853whrES56ibW.png

    如果不知道原圖的尺寸該怎麼辦?我們可以在 <Image> 包一層父元素,並在<Image> 加上 fill 這個 prop,來讓圖片填滿父元素

    但因為加上 fill 後,圖片會是 position absolute,必須讓父元素為 position relative:

      {/* 父層必須是 position relative */}
      <div className='w-[500px] h-[500px] relative'>
        <Image
          src='...'
          alt='hero image'
          fill
        />
      </div>
    

    以上述例子,圖片 loading 時預留的空間,和最終顯示的尺寸都會是 500x500。

    也可以使用 sizesstyle 進一步調整圖片尺寸:

       <Image
          src='...'
          alt='hero image'
          fill
          sizes="100vw"
          style={{
            objectFit: 'cover',
          }}
        />
    
  3. 須手動設定 blurDataURL
    remote 圖片使用 placeholder="blur",須提供 placeholder 圖片的 Data URL

    篇幅關係不多做說明,假如想使用簡單色塊,可以使用一些現成服務生成 Data URL:

    <Image
        src='...'
        alt='hero image'
        placeholder='blur'
        blurDataURL='data:...'
        width={500}
        height={500}
    />
    

假如要搭配 background-image、image-set、canvas 中的 context.drawImage(),或是 <picture>,沒辦法直接使用 <Image>,v13.5 有推出一個實驗 function: unstable_getImgProps(),有需要可參考官方文件


還記得我們我們在 Day 07 的開頭,我們提到 Next.js 13 的更新主要有三大重點:

  1. Compiler Infrastructure 優化
  2. Rendering Infrastructure 優化
  3. Component Toolkit

Compiler Infrastructure 的優化我們在 Day 07 介紹了 Turbopack;Rendering Infrastructure 的優化我們介紹了新的路由架構 App Router,和新的 components 生成模式 Server Components。

而 Component Toolkit,則是 Next 有針對部分靜態文件提供了「優化版本」的 components,next/image<Image> 就是其中之一。

除了圖片以外,Next 也有提供載入字型的優化。但因為篇幅考量,暫時就先不介紹。有需要的讀者可以參考官方文件:

Font Optimization

最後做個小補充:
Web Almanac 2022 的報告,假如看網頁單一 reponse 中各類型檔案的大小,則是影片佔比最大。
response size

所以我就查了一下,Next 有沒有針對影片優化,提供類似 <Image> 的 component。發現 2020 年有人發 issue 建議過官方,但時至今日官方還沒任何回覆。不過底下有些留言有提供在 Next 做影片效能優化的方法,有需要的讀者可以參考 issue!

以上就 Next 針對圖片優化的介紹,明天會接著討論 JavaScript 載入的優化。

謝謝大家耐心的閱讀,我們明天見!


上一篇
Day 25 - Next.js 13.5:Server Actions 原理與現行版本使用考量
下一篇
Day 27 - JavaScript 載入優化:next/dynamic
系列文
深入淺出,完整認識 Next.js 13 !30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言